﻿using Microsoft.Xna.Framework;
using System;
using fess.Graphics;
using fess.Maps;
using fess.RPG;
using System.Collections.Generic;
using System.Linq;


namespace fess
{
  interface IActive
  {
    bool Update(TimeSpan time);
  }

  interface IAI : IActive
  {
    double Priority { get; }
  }

  class GotoAI : IAI
  {
    internal enum State { NearTarget, Going, Unblocking };
    ActionCharacter me;
    State currentState = State.NearTarget;
    Vector2 blockedPoint = Vector2.Zero, exploreDir = Vector2.Zero;
    public Vector2 targetPos, targetDir;
    float closeDist = 0.1f, farDist = 0.5f;
    public bool stop = false;

    public GotoAI(ActionCharacter me, Vector2 target, Vector2 targetDir)
    {
      this.me = me;
      this.targetPos = target;
      this.targetDir = targetDir;
    }

    public virtual bool Update(TimeSpan time)
    {
      if (me.Dead || stop)
        return false;

      var dir = targetPos - me.Character.Position;
      var dist = dir.Length();
      switch (currentState)
      {
        case State.NearTarget:
          if (dist > farDist)
            currentState = State.Going;

          break;
        case State.Going:
          if (dist < closeDist)
          {
            currentState = State.NearTarget;
            me.Character.Direction = targetDir - me.Character.Position;
          }
          else
          {
            float moveDist = (float)me.Speed * (float)Map.game.elapsedTime.TotalMilliseconds;

            if (!me.Shift(dir.LimitLength(moveDist)))
            { // blocked, try going in one direction
              if (dir.X == 0 || dir.Y == 0)
              { // we are stuck facing the target, try going to the sides
                currentState = State.Unblocking;
                blockedPoint = me.Character.Position;
                exploreDir = (dir.X == 0 ? Vector2.UnitX : Vector2.UnitY) * 100 * (Misc.Rand.Next(2) == 0 ? 1 : -1);
                break;
              }

              var unblockDir = new Vector2(0, dir.Y);
              if (!me.Shift(unblockDir.LimitLength(moveDist)))
              {
                unblockDir = new Vector2(dir.X, 0);
                me.Shift(unblockDir.LimitLength(moveDist));
              }
            }
          }

          break;

        case State.Unblocking:
          {
            float moveDist = (float)me.Speed * (float)Map.game.elapsedTime.TotalMilliseconds;
            if (me.Shift(dir.LimitLength(moveDist)))
              currentState = State.Going; // unblocked!
            else
            {
              if ((blockedPoint - me.Character.Position).Length() > 400)
                exploreDir = -exploreDir;

              if (!me.Shift(exploreDir.LimitLength(moveDist)))
                exploreDir = -exploreDir;
            }

            break;
          }
      }

      return true;
    }

    internal ActionCharacter Me
    {
      get { return me; }
      set { me = value; }
    }

    internal GenericMap Map
    {
      get { return me.Map; }
    }

    internal State CurrentState
    {
      get { return currentState; }
      set { currentState = value; }
    }

    public float FarDist
    {
      get { return farDist; }
      set { farDist = value; }
    }

    public float CloseDist
    {
      get { return closeDist; }
      set { closeDist = value; }
    }

    public virtual double Priority { get { return 0.5; } }
  }

  class FollowAI: GotoAI
  {
    ActionCharacter target;

    public FollowAI(ActionCharacter me, ActionCharacter target) : base(me, target.Character.Position, target.Character.Position)
    {
      CloseDist = 30;
      FarDist = 50;
      this.target = target;
    }

    public override bool Update(TimeSpan time)
    {
      targetPos = target.Character.Position;
      targetDir = target.Character.Position;
      return base.Update(time);
    }

    internal ActionCharacter Target
    {
      get { return target; }
      set { target = value; }
    }

    public override double Priority { get { return 1; } }
  }

  class MeleeAI: IAI
  {
    enum State { Chasing, Fighting };
    State currentState = State.Chasing;

    FollowAI follower;
    float tooFar = 35;

    public bool enemy;

    public MeleeAI(ActionCharacter me, bool enemy)
    {
      this.enemy = enemy;
      var target = me.Map.GetClosestTarget(me.Character.Position, enemy) ?? (enemy ? me : me.Map.game.fess);
      follower = new FollowAI(me, target) { CloseDist = me.Radius + target.Radius + 5, FarDist = tooFar };
      if (target == me.Map.game.fess)
      {
        follower.CloseDist = 70;
        follower.FarDist = 90;
      }
    }

    public bool Update(TimeSpan time)
    {
      if (follower.Me.Dead)
        return false;

      if (follower.Target == null || follower.Target.Dead || follower.Target == follower.Map.game.fess || follower.Target == follower.Me)
      {
        follower.Target = follower.Map.GetClosestTarget(follower.Me.Character.Position, enemy);
        if (follower.Target == null)
        {
          follower.Target = enemy ? follower.Me : follower.Map.game.fess;
          follower.CloseDist = 70;
          follower.FarDist = 90;
          follower.Update(time);
          return true;
        }
      }

      follower.CloseDist = follower.Me.Radius + follower.Target.Radius + 5;
      follower.FarDist = tooFar;

      switch (currentState)
      {
        case State.Chasing:
          follower.Update(time);
          if (follower.CurrentState == FollowAI.State.NearTarget)
            currentState = State.Fighting;

          break;

        case State.Fighting:
          if ((follower.Me.Character.Position - follower.Target.Character.Position).Length() > tooFar)
            currentState = State.Chasing;
          else
            follower.Me.MeleeAttack(follower.Target);

          break;
      }

      return true;
    }

    public double Priority { get { return 5; } }
  }

  class MageAI : IAI
  {
    ActionCharacter me;
    bool enemy;
    List<Vector2> attackPoints;
    GotoAI move;

    public MageAI(ActionCharacter me, bool enemy, List<Vector2> attackPoints)
    {
      this.me = me;
      this.enemy = enemy;
      this.attackPoints = attackPoints.Select(p => p + Misc.ModArg((float)Misc.Rand.NextDouble() * 30, Misc.RandAngle())).ToList();
      move = new GotoAI(me, me.Character.Position, Vector2.Zero);
      move.CloseDist = 60;
      move.FarDist = 80;
    }

    public double Priority
    {
      get { return 5; }
    }

    public bool IsGood(Vector2 point)
    {
      foreach (var target in me.Map.GetFaction(!enemy))
        if (!target.Dead && CanShoot(point, target.Character.Position))
          return true;

      return false;
    }

    public bool CanShoot(Vector2 from, Vector2 to)
    {
      var dir = to - from;
      if (dir.Length() > 200)
        return false;

      var offset = dir * 33 / dir.Length();
      var target = to - offset;
      return me.Map.MoveObject(from, target, 15, me) == target;
    }

    public bool Update(TimeSpan time)
    {
      var destinations = (from p in attackPoints
                          where IsGood(p)
                          orderby (p - me.Character.Position).Length()
                          select p).ToList();

      if (destinations.Count > 0)
      {
        move.targetPos = destinations[0];
        move.targetDir = me.Map.GetClosestTarget(destinations[0], !enemy).Character.Position;
      }

      move.Update(time);

      if (me.Mana >= FessGame.FireArrowCost)
      {
        ActionCharacter shoot = null;
        bool fireball = false;
        foreach (var target in me.Map.GetFaction(!enemy))
          if (!target.Dead && CanShoot(me.Character.Position, target.Character.Position))
          {
            shoot = target;
            if (me.Mana < FessGame.FireballCost)
              break;

            var closest = me.Map.GetClosestTarget(target.Character.Position, !enemy);
            if (closest == null || (closest.Character.Position - target.Character.Position).Length() > 100)
            {
              fireball = true;
              break;
            }
          }

        if (shoot != null)
        {
          if (fireball)
            me.Fireball(shoot.Character.Position);
          else
            me.FireArrow(shoot.Character.Position);
        }
      }

      return true;
    }
  }

}